using System;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using Microsoft.SharePoint.Publishing.Navigation;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint;

namespace GoodStuff.SharePoint2007
{
    /// <summary>
    /// Navigation builder class for render an UL-LI menu, default based on the CombinedNavSiteMapProvider
    /// </summary>
    [ToolboxData("<{0}:Navigation runat=\"server\" />")]
    public class Navigation : SPControl, INamingContainer
    {
        /// <summary>
        /// Default Constructor of "Navigation"
        /// </summary>
        public Navigation()
        {
            //some defaults can be put here            
        }

        /// <summary>
        /// A site map provider of type PortalSiteMapProvider. Default is CombinedNavSiteMapProvider
        /// </summary>
        [Browsable(true)]
        [DefaultValue("")]        
        public string SiteMapProvider
        {
            get
            {
                return _siteMapProvider;
            }
            set
            {
                _siteMapProvider = value;
            }
        }

        /// <summary>
        /// Server relative URL for a starting sub-site. Example /PressReleases/2006. Will be overriden if a VitensNavigationTarget is found based on a HttpCookie.
        /// Default is the root site when not configured and no VitensNavigationTarget is found in the HttpCookieCollection.
        /// </summary>
        [Browsable(true)]
        [DefaultValue("")]
        public string StartNodeKey {get;set;}

        /// <summary>
        /// Include sub-sites? Allowed values are True, False, or PerWeb. Default is PerWeb, i.e., a per sub-site setting, configurable in the Modify Navigation Site Actions menu.
        /// </summary>
        [Browsable(true)]
        [DefaultValue("")]
        public string IncludeSubSites
        {
            get
            {
                return _includeSubSites;
            }
            set
            {
                _includeSubSites = value;
            }
        }

        /// <summary>
        ///  Include pages? Allowed values are True, False, or PerWeb. Default is PerWeb, i.e., a per sub-site setting, configurable in the Modify Navigation Site Actions menu.
        /// </summary>
        [Browsable(true)]
        [DefaultValue("")]
        public string IncludePages
        {
            get
            {
                return _includePages;
            }
            set
            {
                _includePages = value;
            }
        }

        /// <summary>
        ///  Include headings? Allowed values are True, False.
        /// </summary>
        [Browsable(true)]
        [DefaultValue(true)]
        public bool IncludeHeadings
        {
            get
            {
                return _includeHeadings;
            }
            set
            {
                _includeHeadings = value;
            }
        }

        /// <summary>
        ///  Include authored links? Allowed values are True, False.
        /// </summary>
        [Browsable(true)]
        [DefaultValue(true)]
        public bool IncludeAuthoredLinks
        {
            get
            {
                return _includeAuthoredLinks;
            }
            set
            {
                _includeAuthoredLinks = value;
            }
        }

        /// <summary>
        /// Maximum number of node levels. Default is 0, i.e., no limit.
        /// </summary>
        [Browsable(true)]
        [DefaultValue(0)]
        public int MaxLevels
        {
            get
            {
                return _maxLevels;
            }
            set
            {
                _maxLevels = value;
            }
        }

        /// <summary>
        /// If set to True, the navigation only expands the root nodes and all parent nodes to the current node. If set to False, sibling nodes to the current node and its parent nodes will also be shown.
        /// </summary>
        [Browsable(true)]
        [DefaultValue(false)]
        public bool CompactMode
        {
            get
            {
                return _compactMode;
            }
            set
            {
                _compactMode = value;
            }
        }

        /// <summary>
        /// CSS class name for the outermost &lt;ul&gt; tag.
        /// </summary>
        [Browsable(true)]
        [DefaultValue("")]
        public string ListCssClass
        {
            get
            {
                return _listCssClass;
            }
            set
            {
                _listCssClass = value;
            }
        }

        /// <summary>
        /// Id for the outermost &lt;ul&gt; tag.
        /// </summary>
        [Browsable(true)]
        [DefaultValue("")]
        public string ListId
        {
            get
            {
                return _listId;
            }
            set
            {
                _listId = value;
            }
        }

        /// <summary>
        /// CSS class name for regular hyperlinked nodes.
        /// </summary>
        [Browsable(true)]
        [DefaultValue("")]
        public string NodeCssClass
        {
            get
            {
                return _nodeCssClass;
            }
            set
            {
                _nodeCssClass = value;
            }
        }

        /// <summary>
        /// CSS class name for a selected node.
        /// </summary>
        [Browsable(true)]
        [DefaultValue("")]
        public string CurrentNodeCssClass
        {
            get
            {
                return _currentNodeCssClass;
            }
            set
            {
                _currentNodeCssClass = value;
            }
        }

        /// <summary>
        /// CSS class name for the lowest level node under which there is a selected node.
        /// </summary>
        [Browsable(true)]
        [DefaultValue("")]
        public string CurrentNodeParentCssClass
        {
            get
            {
                return _currentNodeParentCssClass;
            }
            set
            {
                _currentNodeParentCssClass = value;
            }
        }

        /// <summary>
        /// CSS class name for nodes with no URL, like headings.
        /// </summary>
        [Browsable(true)]
        [DefaultValue("")]
        public string NoUrlNodeCssClass
        {
            get
            {
                return _noUrlNodeCssClass;
            }
            set
            {
                _noUrlNodeCssClass = value;
            }
        }

        /// <summary>
        /// True when menu should always expand all items. False when expanding is determined based on currentNode
        /// </summary>
        [Browsable(true)]
        [DefaultValue(true)]
        public bool ExpandAllways
        {
            get { return _expandAllways; }
            set {_expandAllways = value;}
        }

        private const string TopListOpenHtmlFormatString = "<ul{0}>";

        private const string DisplayBlock = "block";
        private const string DisplayNone = "none";
        private const string ListCloseHtml = "\n</ul>";
        private const string ListItemOpenHtml = "\n<li>";
        private const string ListItemCloseHtml = "</li>";
        private const string ListOpenHtml = "\n<ul>";
        private const string ToggleImageIDSuffix = "x";

        private PortalSiteMapProvider _provider;
        private bool _compactMode = false;
        private bool _includeAuthoredLinks = true;
        private bool _includeHeadings = true;
        private int _maxLevels = 0;
        private string _currentNodeCssClass;
        private string _currentNodeParentCssClass;
        private string _includePages = String.Empty;
        private string _includeSubSites = String.Empty;
        private string _listCssClass;
        private string _listId;
        private string _nodeCssClass;
        private string _noUrlNodeCssClass;
        private string _siteMapProvider = "CombinedNavSiteMapProvider";
        private bool _expandAllways = true;

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            SiteMapProvider siteMapProvider = SiteMap.Providers[_siteMapProvider];
            if (siteMapProvider == null)
            {
                return;
            }

            InitPortalSiteMapProvider(siteMapProvider);
        }

        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            try
            {
                if (_provider == null)
                {
                    throw new HttpException(String.Format("Invalid SiteMapProvider: {0}", _siteMapProvider));
                }

                String cssClassHtml = String.Empty;
                if (!String.IsNullOrEmpty(_listCssClass))
                {
                    cssClassHtml = String.Format(" class=\"{0}\"", _listCssClass);
                }
                if (!string.IsNullOrEmpty(_listId))
                {
                    cssClassHtml = cssClassHtml + string.Format(" id=\"{0}\"", _listId);
                }
                Controls.Add(new LiteralControl(String.Format(TopListOpenHtmlFormatString, cssClassHtml)));

                SiteMapNode startNode = GetStartingNode();
                SiteMapNodeCollection nodes = _provider.GetChildNodes(startNode);
                foreach (SiteMapNode childrenNode in nodes)
                {
                    RenderNode(childrenNode, 1);
                }

                Controls.Add(new LiteralControl(ListCloseHtml));
            }
            catch (Exception ex)
            {
                Controls.Add(new LiteralControl(ex.Message));
            }
        }

        private void InitPortalSiteMapProvider(SiteMapProvider siteMapProvider)
        {
            if (siteMapProvider is PortalSiteMapProvider)
            {
                _provider = siteMapProvider as PortalSiteMapProvider;
                _provider.DynamicChildLimit = 0;
                _provider.EncodeOutput = true;
                _provider.IncludeAuthoredLinks = _includeAuthoredLinks;
                _provider.IncludeHeadings = _includeHeadings;
                _provider.IncludePages = GetIncludeOption(_includePages);
                _provider.IncludeSubSites = GetIncludeOption(_includeSubSites);
            }
        }

        private PortalSiteMapProvider.IncludeOption GetIncludeOption(string value)
        {
            switch (value.ToLower())
            {
                case "true":
                    return PortalSiteMapProvider.IncludeOption.Always;
                case "false":
                    return PortalSiteMapProvider.IncludeOption.Never;
                default:
                    return PortalSiteMapProvider.IncludeOption.PerWeb;
            }
        }

        private SiteMapNode GetStartingNode()
        {
            /* Check if we have to use a starting node
             * 1) We use the startNode configured for this control.
             * 2) If not found/configured, we try to determine the root by the current url.
             * 3) If all other options didn't give us a starting node, we use the rootnode as default.
             */

            // 1) StartNodeKey
            if (!string.IsNullOrEmpty(this.StartNodeKey))
            {
                return _provider.FindSiteMapNodeFromKey(this.StartNodeKey);
            }

            return _provider.RootNode;
        }

        private void RenderNode(SiteMapNode node, int level)
        {
            if (ExpandAllways == false && ShowNode(node, level) == false)
            {
                return;
            }

            string cssClass = GetCssClass(node, level);
            Controls.Add(new LiteralControl(ListItemOpenHtml));

            RenderNodeItem(node, cssClass);

            if (IsNodeExpandable(node, level))
            {
                Controls.Add(new LiteralControl(ListOpenHtml));

                SiteMapNodeCollection nodes = _provider.GetChildNodes(node);

                foreach (SiteMapNode childrenNode in nodes)
                {
                    RenderNode(childrenNode, level + 1);
                }

                Controls.Add(new LiteralControl(ListCloseHtml));
            }

            Controls.Add(new LiteralControl(ListItemCloseHtml));
        }

        private bool ShowNode(SiteMapNode node, int level)
        {
            if (level <= 1 || _provider.CurrentNode.IsDescendantOf(node) || node.Key == _provider.CurrentNode.Key)
            {
                return true;
            }

            SiteMapNode parentNode = node.ParentNode;
            if (parentNode == null || _provider.CurrentNode.ParentNode == null)
            {
                return false;
            }

            if (parentNode.Key == _provider.CurrentNode.Key ||
               (parentNode.Key == _provider.CurrentNode.ParentNode.Key && !IsNodeExpandable(_provider.CurrentNode, level)) ||
               (!_compactMode && _provider.CurrentNode.IsDescendantOf(parentNode)))
            {
                return true;
            }

            return false;
        }

        private bool IsNodeExpandable(SiteMapNode node, int level)
        {
            SiteMapNodeCollection nodes = _provider.GetChildNodes(node);

            if (nodes.Count == 0)
            {
                return false;
            }

            if (level >= _maxLevels && _maxLevels != 0)
            {
                return false;
            }

            return true;
        }

        private string GetCssClass(SiteMapNode node, int level)
        {
            if (node.Key == _provider.CurrentNode.Key)
            {
                if (node.Url.StartsWith(Page.Request.Url.AbsolutePath))
                {
                    return _currentNodeCssClass;
                }
                else
                {
                    return _currentNodeParentCssClass;
                }
            }
            else if (String.IsNullOrEmpty(node.Url))
            {
                return _noUrlNodeCssClass;
            }
            else
            {
                if (_provider.CurrentNode.IsDescendantOf(node) && level == _maxLevels)
                {
                    return _currentNodeParentCssClass;
                }
                else
                {
                    return _nodeCssClass;
                }
            }
        }

        private void RenderNodeItem(SiteMapNode node, string cssClass)
        {
            Panel panel = new Panel();
            panel.CssClass = cssClass;

            if (String.IsNullOrEmpty(node.Url))
            {
                panel.Controls.Add(new LiteralControl(node.Title));
            }
            else if (node.Key == _provider.CurrentNode.Key && node.Url.StartsWith(Page.Request.Url.AbsolutePath))
            {
                panel.Controls.Add(new LiteralControl(node.Title));
            }
            else
            {
                HyperLink hyperlink = new HyperLink();

                /* http://support.microsoft.com/kb/953457
                 * This behavior occurs because the navigation functionality in SharePoint Server 2007 depends on 
                 * an underlying cache that is shared by many features. The cache converts all URLs to uppercase 
                 * to perform a case-insensitive hash operation. Then, the cache stores the uppercase URLs to avoid 
                 * performing repeated uppercase conversions. Therefore, in certain situations, uppercase URLs are 
                 * displayed in the rendered navigation links
                 * ==> We perform a .ToLower() on the url of all hyperlinks we render (workaround only)*/
                if (!string.IsNullOrEmpty(node.Url))
                {
                    hyperlink.NavigateUrl = node.Url.ToLower();
                }

                if (node is PortalSiteMapNode)
                {
                    PortalSiteMapNode portalNode = node as PortalSiteMapNode;
                    if (portalNode.Target != null)
                    {
                        hyperlink.Target = portalNode.Target;
                    }
                }
                hyperlink.Text = node.Title;
                panel.Controls.Add(hyperlink);
            }

            Controls.Add(panel);
        }
    }
}